package com.farm.common.exception;

import com.farm.common.constant.CommonConstants;
import com.farm.common.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;

/**
 * 全局捕获异常
 * 1.捕获返回json格式
 * 2.捕获返回页面
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 错误日志模板
     */
    public static final String LOG_ERROR_INFO = "http响应码：{}，错误代码：{}，客户端错误信息：{}，服务端错误信息：{}";

    /**
     * 请求对象
     */
    @Resource
    private HttpServletRequest request;

    /**
     * 响应对象
     */
    @Resource
    private HttpServletResponse response;

    /**
     * 全局处理自定义异常
     */
    @ExceptionHandler(BasicException.class)
    public R<String> clientExceptionDispose(BasicException exception){
        // 设置响应码
        response.setStatus(exception.getStatus());
        // 打印错误日志
        log.error(GlobalExceptionHandler.LOG_ERROR_INFO, exception.getStatus(), exception.getCode(), exception.getClientMessage(), exception.getServerMessage());
        // 堆栈跟踪
        exception.printStackTrace();
        R<String> result = new R<>();
        result.setCode(CommonConstants.FAIL);
        result.setMsg(exception.getClientMessage());
        result.setData(exception.getClientMessage());
        return result;
    }

    /**
     * 捕获意料之外的异常Exception
     */
    @ExceptionHandler(Throwable.class)
    public R<String> otherErrorDispose(Throwable exception){
        BasicException serverException = new ServerException(ServerExceptionEnum.SERVER_ERROR);
        this.response.setStatus(serverException.status);
        // 打印错误日志
        log.error(GlobalExceptionHandler.LOG_ERROR_INFO, serverException.status, serverException.code, serverException.clientMessage, exception.getMessage());
        // 堆栈跟踪
        exception.printStackTrace();
        R<String> result = new R<>();
        result.setCode(CommonConstants.FAIL);
        result.setMsg(exception.getMessage());
        result.setData(exception.getMessage());
        return result;
    }

    /*==========================================================================
        常见自定义异常
    ==========================================================================*/

    /**
     * 400 Bad Request
     * 因发送的请求语法错误,服务器无法正常读取.
     */
    @ExceptionHandler(value = {
            BindException.class,
            MethodArgumentNotValidException.class,
            HttpMessageNotReadableException.class
    })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R<String> ValidExceptionDispose(Exception exception){
        List<String> messages = new ArrayList<>();
        if (exception instanceof BindException) {
            List<ObjectError> list = ((BindException) exception).getAllErrors();
            for (ObjectError item : list) {
                messages.add(item.getDefaultMessage());
            }
        } else if (exception instanceof HttpMessageNotReadableException) {
            messages.add("请求参数丢失");
        } else {
            messages.add(((MethodArgumentNotValidException)exception).getBindingResult().getFieldError().getDefaultMessage());
        }

        String message = String.join(",", messages);
        // 打印错误日志
        log.error(GlobalExceptionHandler.LOG_ERROR_INFO, HttpStatus.BAD_REQUEST.value(), "VALIDATION", message, exception.getMessage());
        // 堆栈跟踪
        exception.printStackTrace();

        R<String> result = new R<>();
        result.setCode(CommonConstants.FAIL);
        result.setMsg(exception.getMessage());
        result.setData(message);
        return result;
    }

    /**
     * 403 Access Forbidden
     * 客户端的请求已经被服务器收到，但由于权限问题，服务器拒绝了该请求。
     */
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public R<String> AccessDenied() {
        R<String> result = new R<>();
        result.setCode(CommonConstants.FAIL);
        result.setMsg(ClientExceptionEnum.NOT_AUTHORIZATION.getClientMessage());
        result.setData(ClientExceptionEnum.NOT_AUTHORIZATION.getServerMessage());
        return result;
    }

    /**
     * 404 Not Found
     * 请求失败，请求所希望得到的资源未被在服务器上发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。假如服务器知道情况的
     * 应当使用410状态码来告知旧资源因为某些内部的配置机制问题，已经永久的不可用，而且没有任何可以跳转的地址。
     * 404这个状态码被广泛应用于当服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。出现这个错误的最有可能的原因是服务器端没有这个页面。
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public R<String> notFound() {
        R<String> result = new R<>();
        result.setCode(CommonConstants.FAIL);
        result.setMsg(ClientExceptionEnum.NOT_FOUND.getClientMessage());
        result.setData(ClientExceptionEnum.NOT_FOUND.getServerMessage());
        return result;
    }

    /**
     * 405 Method Not Allowed
     * 请求行中指定的请求方法不能被用于请求相应的资源。该响应必须返回一个Allow 头信息用以表示出当前资源能够接受的请求方法的列表。
     *
     * 鉴于 PUT，DELETE 方法会对服务器上的资源进行写操作，因而绝大部分的网页服务器都不支持或者在默认配置下不允许上述请求方法，对于此类请求均会返回405错误。
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public R<String> methodNotAllowed() {
        R<String> result = new R<>();
        result.setCode(CommonConstants.FAIL);
        result.setMsg(ClientExceptionEnum.METHOD_NOT_ALLOWED.getClientMessage());
        result.setData(ClientExceptionEnum.METHOD_NOT_ALLOWED.getServerMessage());
        return result;
    }

}